using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Xml;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Utilities;

namespace MyLinksMenu.Features.MyLinksMenuWebConfig
{
    /// <summary>
    /// This class handles events raised during feature activation, deactivation, installation, uninstallation, and upgrade.
    /// </summary>
    /// <remarks>
    /// The GUID attached to this class may be used during packaging and should not be modified.
    /// </remarks>

    [Guid("bfd83795-bf8b-4a92-a4f3-a4f7103766c9")]
    public class MyLinksMenuWebConfigEventReceiver : SPFeatureReceiver
    {
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            SPWebApplication webApplication = (SPWebApplication)properties.Feature.Parent;
            ApplyWebConfigModification(webApplication, "config.MyLinksMenu.xml", ConfigMode.Install);
        }

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            SPWebApplication webApplication = (SPWebApplication)properties.Feature.Parent;
            ApplyWebConfigModification(webApplication, "config.MyLinksMenu.xml", ConfigMode.Remove);
        }

        public static void ApplyWebConfigModification(SPWebApplication webApplication, String filename, ConfigMode configMode)
        {
            foreach (ModificationEntry mod in GetConfigurationMods(filename))
                AddRemoveMod(mod, webApplication, configMode);
        }

        private static void AddRemoveMod(ModificationEntry mod, SPWebApplication chosenWebApp, ConfigMode configMode)
        {
            SPWebConfigModification newModification = CreateModification(mod);
            List<SPWebConfigModification> modsToDelete = chosenWebApp.WebConfigModifications.Where(
                configModification => IsConfigModificationsEqual(newModification, configModification)).ToList();

            if (modsToDelete.Count > 0)
            {
                foreach (SPWebConfigModification modToDelete in modsToDelete)
                {
                    chosenWebApp.WebConfigModifications.Remove(modToDelete);
                }

                chosenWebApp.Update(true);
                chosenWebApp.WebService.ApplyWebConfigModifications();
            }

            if (configMode == ConfigMode.Install)
            {
                chosenWebApp.WebConfigModifications.Add(newModification);
                chosenWebApp.Update(true);
                chosenWebApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
            }
        }

        private static bool IsConfigModificationsEqual(SPWebConfigModification modification, SPWebConfigModification configModification)
        {
            if (modification.Name != configModification.Name)
                return false;

            if (modification.Owner != configModification.Owner)
                return false;

            if (modification.Path != configModification.Path)
                return false;

            if (modification.Sequence != configModification.Sequence)
                return false;

            if (modification.Type != configModification.Type)
                return false;

            if (modification.Value != configModification.Value)
                return false;

            return true;
        }

        private static SPWebConfigModification CreateModification(ModificationEntry modEntry)
        {
            SPWebConfigModification mod = new SPWebConfigModification(modEntry.Name, modEntry.Path);
            mod.Owner = modEntry.Owner;
            mod.Sequence = 0;
            mod.Type = modEntry.ModType;
            mod.Value = modEntry.Value;
            return mod;
        }

        private static IEnumerable<ModificationEntry> GetConfigurationMods(string filename)
        {
            List<ModificationEntry> mods = new List<ModificationEntry>();
            List<ModificationEntry> xmlMods = GetCurrentMods(filename);

            if (xmlMods != null && xmlMods.Count > 0)
            {
                foreach (ModificationEntry xmlMod in xmlMods)
                {
                    mods.Add(xmlMod);
                }
            }

            return mods;
        }

        private static XmlDocument GetFileDoc(string filename)
        {
            XmlDocument configEntryDocument = new XmlDocument();
            configEntryDocument.PreserveWhitespace = true;
            string path = SPUtility.GetGenericSetupPath("CONFIG") + "\\" + filename;
            configEntryDocument.Load(path);

            return configEntryDocument;
        }

        private static List<ModificationEntry> GetCurrentMods(string filename)
        {

            List<ModificationEntry> mods = new List<ModificationEntry>();
            XmlDocument fileDoc = GetFileDoc(filename);
            XmlNode installNode = fileDoc.SelectSingleNode("/ConfigEntries");
            if (installNode != null && installNode.HasChildNodes && installNode.Attributes != null)
            {
                XmlAttribute ownerAttrib = installNode.Attributes["owner"];
                String owner = ownerAttrib.Value;
                mods.AddRange(installNode.ChildNodes.OfType<XmlElement>().Select(node => GetModFromNode(node, owner)));
            }

            return mods;
        }

        private static ModificationEntry GetModFromNode(XmlNode node, String owner)
        {
            ModificationEntry mod = new ModificationEntry();
            mod.Owner = owner;

            foreach (XmlNode childNode in node.ChildNodes)
            {
                if (childNode is XmlElement)
                {
                    XmlElement element = childNode as XmlElement;
                    if (element.Name == "Name")
                    {
                        mod.Name = GetElementValue(element.InnerText);
                        continue;
                    }

                    if (element.Name == "Path")
                    {
                        mod.Path = GetElementValue(element.InnerText);
                        continue;
                    }

                    if (element.Name == "Value")
                    {
                        mod.Value = GetElementValue(element.InnerText);
                        continue;
                    }

                    if (element.Name == "ModType")
                    {
                        mod.ModType = (SPWebConfigModification.SPWebConfigModificationType)Enum.Parse(typeof(SPWebConfigModification.SPWebConfigModificationType), GetElementValue(element.InnerText), true);
                        continue;
                    }
                }
            }

            return mod;
        }

        private static string GetElementValue(string rawValue)
        {
            if (string.IsNullOrEmpty(rawValue))
                return rawValue;

            string elementValue = rawValue.Replace("<![CDATA[", "");
            elementValue = elementValue.Replace("]]>", "");

            return elementValue;
        }

        public enum ConfigMode
        {
            Install,
            Remove
        }

        private struct ModificationEntry
        {
            public string Name;
            public string Path;
            public string Value;
            public string Owner;
            public SPWebConfigModification.SPWebConfigModificationType ModType;

            public ModificationEntry(string name, string path, string value, string owner, SPWebConfigModification.SPWebConfigModificationType modType)
            {
                Name = name;
                Path = path;
                Value = value;
                ModType = modType;
                Owner = owner;
            }
        }
    }
}
